package com.gitee.wsl.mathematics.geometry.d2.contour.box

import com.gitee.wsl.mathematics.function.Scalars
import com.gitee.wsl.mathematics.geometry.Interval
import com.gitee.wsl.mathematics.vector.Vector
import com.gitee.wsl.mathematics.vector.ext.approx
import com.gitee.wsl.mathematics.vector.ext.every
import com.gitee.wsl.mathematics.vector.ext.nth
import com.gitee.wsl.mathematics.vector.ext.zip
import com.gitee.wsl.mathematics.vector.vec2.Vec2f
import com.gitee.wsl.mathematics.vector.vec2.Vector2
import kotlin.math.max
import kotlin.math.min
import kotlin.math.sqrt


/**
 * @author ztellman
 */
abstract class Box<T : Vector<Float, T>, U : Box<T, U>> {

    abstract fun lower(): T
    abstract fun upper(): T

    abstract val isEmpty: Boolean

    protected abstract fun construct(a: T, b: T): U
    protected abstract fun empty(): U

    fun distanceSquared(point: T): Float {
        val l: T = lower() - point
        val u: T = point - upper()
        return u.zip(l) { a, b ->
            a.run { maxOf(0f, a, b) }
        }.squaredLength
    }

    fun distance(point: T): Float {
        return sqrt(distanceSquared(point))
    }

    fun union(b: U): U {
        if (isEmpty) {
            return b
        } else if (b.isEmpty) {
            @Suppress("UNCHECKED_CAST")
            return this as U
        }
        return construct(
            lower().zip(b.lower()) { x, y -> min(x, y) },
            upper().zip(b.upper()) { x, y -> max(x, y) }
        )
    }

    fun union(v: T): U {
        return if (isEmpty) {
            construct(v, v)
        } else construct(
            lower().zip(v) { a, b -> min(a, b) },
            upper().zip(v) { a, b -> max(a, b) }
        )
    }

    fun intersection(b: U): U {
        return if (isEmpty || b.isEmpty || !intersects(b)) {
            empty()
        } else construct(
            lower().zip(b.lower()) { u, v -> max(u, v) },
            upper().zip(b.upper()) { u, v -> min(u, v) }
        )
    }

    open fun intersects(b: U): Boolean {
        return if (isEmpty || b.isEmpty) {
            false
        } else (b.upper()-lower()).every(NOT_NEGATIVE)
                && (upper()-b.lower()).every(NOT_NEGATIVE)
    }

    operator fun contains(v: T): Boolean {
        return ((v-lower()).every(NOT_NEGATIVE)
                && (upper()-v).every(NOT_NEGATIVE))
    }

    fun nth(idx: Int): Interval {
        return Interval.interval(lower().nth(idx).toDouble(), upper().nth(idx).toDouble())
    }

    fun clamp(v: T): T {
        return v.zip(lower()) { a, b -> max(a, b) }.
                 zip(upper()) { a, b -> min(a, b) }
    }

    fun size(): T {
        return upper()-lower()
    }

    fun normalize(v: T): T {
        return v-(lower()).div(size())
    }

    fun lerp(t: Double): T {
        return lower()+(size()*t)
    }

    fun lerp(v: T): T {
        return lower()+(size()*v)
    }

    fun translate(v: T): U {
        return construct(lower()+v, upper()+v)
    }

    fun scale(v: T): U {
        return construct(lower()*v, upper()*v)
    }

    fun expand(t: Double) = expand(t.toFloat())

    fun expand(t: Float): U {
        if (isEmpty) {
            @Suppress("UNCHECKED_CAST")
            return this as U
        }
        val nLower: T = lower() - t
        val nUpper: T = upper() + t
        return if ((nUpper - nLower).every(NOT_NEGATIVE)) construct(nLower, nUpper) else empty()
    }

    fun expand(v: T): U {
        if (isEmpty) {
            @Suppress("UNCHECKED_CAST")
            return this as U
        }
        val nLower: T = lower()-v
        val nUpper: T = upper()+v
        return if ((nUpper-nLower).every(NOT_NEGATIVE)) construct(nLower, nUpper) else empty()
    }

    override fun hashCode(): Int {
        return if (isEmpty) {
            0
        } else 31 * lower().hashCode() xor upper().hashCode()
    }

    override fun equals(other: Any?): Boolean {
        if (other is Box<*, *>) {
            val b = other
            return if (isEmpty) {
                b.isEmpty
            } else lower() == b.lower() && upper() == b.upper()
        }
        return false
    }

    override fun toString(): String {
        return "[" + lower() + ", " + upper() + "]"
    }

    companion object {
        fun box(a: Vec2f, b: Vec2f): Box2 {
            return Box2(a, b)
        }

        fun box(a: Interval, b: Interval): Box2 {
            return Box2(Vec2f(a.lo, b.lo), Vec2f(a.hi, b.hi))
        }

        /*fun box(a: Vec3f, b: Vec3f): Box3 {
            return Box3(a, b)
        }

        fun box(a: Vec4, b: Vec4): Box4 {
            return Box4(a, b)
        }*/

        private val POSITIVE = { d: Float -> d > 0 }
        private val NOT_NEGATIVE = { d: Float -> d >= 0 }

        fun <T : Vector2<Float, T>, U : Box<T, U>> equals(
            a: Box<T, U>,
            b: Box<T, U>,
            epsilon: Double
        ): Boolean {
            return a.lower()  approx  b.lower() && a.upper() approx b.upper()
        }
    }
}